namespace MoreLinq;

using System;
using System.Collections.Generic;

static partial class MoreEnumerable
{
    /// <summary>
    /// Removes elements from the end of a sequence as long as a specified condition is true.
    /// </summary>
    /// <typeparam name="T">Type of the source sequence.</typeparam>
    /// <param name="source">The source sequence.</param>
    /// <param name="predicate">The predicate to use to remove items from the tail of the sequence.</param>
    /// <returns>
    /// An <see cref="IEnumerable{T}"/> containing the source sequence elements except for the bypassed ones at the end.
    /// </returns>
    /// <exception cref="ArgumentNullException">The source sequence is <see langword="null"/>.</exception>
    /// <exception cref="ArgumentNullException">The predicate is <see langword="null"/>.</exception>
    /// <remarks>
    /// This operator uses deferred execution and streams its results. At any given time, it
    /// will buffer as many consecutive elements as satisfied by <paramref name="predicate"/>.
    /// </remarks>

    public static IEnumerable<T> SkipLastWhile<T>(this IEnumerable<T> source, Func<T, bool> predicate)
    {
            if (source == null) throw new ArgumentNullException(nameof(source));
            if (predicate == null) throw new ArgumentNullException(nameof(predicate));

            return source.TryAsListLike() switch
            {
                { } list => IterateList(list, predicate),
                _ => IterateSequence(source, predicate),
            };

            static IEnumerable<T> IterateList(ListLike<T> list, Func<T, bool> predicate)
            {
                var i = list.Count - 1;
                while (i >= 0 && predicate(list[i]))
                {
                    i--;
                }

                for (var j = 0; j <= i; j++)
                {
                    yield return list[j];
                }
            }

            static IEnumerable<T> IterateSequence(IEnumerable<T> source, Func<T, bool> predicate)
            {
                Queue<T>? queue = null;
                foreach (var item in source)
                {
                    if (predicate(item))
                    {
                        queue ??= new Queue<T>();
                        queue.Enqueue(item);
                    }
                    else
                    {
                        while (queue?.Count > 0)
                        {
                            yield return queue.Dequeue();
                        }
                        yield return item;
                    }
                }
            }
        }
}
